lightning address
2025-08-25 ยท 3 min read
A lightning address is an email-like string (ex: "philip@lexe.app") that lets users pay to a static human-readable identifier instead of a raw, single-use BOLT11 invoice.
- LUD-16 (Lightning Address): https://github.com/lnurl/luds/blob/luds/16.md
- LUD-06 (payRequest): https://github.com/lnurl/luds/blob/luds/06.md
- LUD-09 (payRequest successAction): https://github.com/lnurl/luds/blob/luds/09.md
- LUD-10 (payRequest successAction aes): https://github.com/lnurl/luds/blob/luds/10.md
- LUD-11 (payRequest disposable): https://github.com/lnurl/luds/blob/luds/11.md
- LUD-12 (payRequest commentAllowed): https://github.com/lnurl/luds/blob/luds/12.md
- LUD-18 (payRequest payerData): https://github.com/lnurl/luds/blob/luds/18.md
- LUD-20 (payRequest long description): https://github.com/lnurl/luds/blob/luds/20.md
- LUD-21 (payRequest verify paid endpoint): https://github.com/lnurl/luds/blob/luds/21.md
payment flow #
- Payer wants to pay "philip@lexe.app".
- Payer client makes a request
GET https://lexe.app/.well-known/lnurlp/philip. - Server responds with an LNURLp JSON blob.
{
"callback": "<callback>",
"minSendable": 1,
"maxSendable": 100000000,
"metadata": "<metadata>",
"commentAllowed": 200,
"tag": "payRequest"
}
or
{
"status": "ERROR",
"reason": "No such user"
}
- Payer client follows LUD-06 (payRequest) flow.
- Payer client makes a request
GET <callback><?|&>amount=<msats>. - Server responds with a JSON blob:
{
"pr": "<bolt11-invoice>",
"routes": []
}
or
{
"status": "ERROR",
"reason": "Amount is too small"
}
Payer client verifies the BOLT11 invoice:
- invoice
description_hashin the BOLT11 invoice matchesSHA256(metadata). - invoice
amountmatches the requested amount (if any).
- invoice
Payer client pays the invoice.
details #
The
metadatastring must be hashed with SHA256 and included in the BOLT11 invoice'sdescription_hashfield.Endpoints needs proper CORS headers so browsers will use them.
design notes (Cloudflare) #
problem #
We currently serve https://lexe.app from Cloudflare pages, but we need to somehow serve https://lexe.app/.well-known/lnurlp/* from our backend gateway service.
solution 1: redirect #
By far the simplest approach is to configure _redirects in our Cloudflare pages repo to redirect requests to /.well-known/lnurlp/* to our backend.
The main downside is that dumb clients that don't follow redirects will break.
solution 2: cloudflare workers reverse proxy #
Unfortunately, there's no origin rewrite rule available until Enterprise tier (>$2000/mo, "call me" pricing). The cloudflare-approved way to do this is to use a Cloudflare Worker function at that path that makes the request to our backend and returns the response.
Fortunately Worker CPU time limits don't apply to time spent waiting for fetch requests.
Pricing: Free: 100k requests/day, 10ms CPU time/request
solution 3: serve LNURLp blobs from Azure blob storage / Cloudflare R2 #
Cloudflare does support serving static files from various object storage providers (Cloudflare R2, AWS S3, Google Cloud Storage, Azure Blob Storage). Because the LNURLp blobs contain a "callback" URL that can point to our actual backend, we can just serve static JSON files from blob storage.
problem #
Our callback will have uncontrolled clients hitting our backend services, which means we need public webpki certs issued.